/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_SDL_H
#define GENERIK_SDL_H

/** \file sdl.h
This file presents a wrapper for the SDL library.
*/

#include <SDL/SDL.h>
//#include "tracing.h"
#include <list>
#include <map>

namespace generik
{

	namespace sdl
	{
	
		struct config
		{
			int width, height;
			int bpp;
			int flags;
		};
		
		struct handler
		{
			virtual void activate()   = 0;
			virtual void deactivate() = 0;

			/** Handle an SDL_KEYUP event.
			The default is to ignore key releases.
			*/
			virtual bool processkeyup(SDL_keysym& keysym)
			{
				return false;
			}
			
			void get_mouse(int& x, int& y)
			{
				SDL_PumpEvents();
				SDL_GetMouseState(&x, &y);
			}

			/** Handle an SDL_KEYDOWN event.
			Default is to return false when the user presses Escape, which
			breaks out of the start() loop. No other keys are processed.
			*/
			virtual bool processkeydown(SDL_keysym& keysym)
			{
				switch (keysym.sym)
				{
					case SDLK_ESCAPE:
						//std::cerr<<"SDLK_ESCAPE down: quitting"<<std::endl;
						//running = false;
						break;
					case SDLK_F12:
						return true;
						break;
					default:
						break;
				}
				return false;	
			}
			/** Handle an SDL_QUIT event.
			Default is to return false, which breaks out of the start() loop.
			*/
			virtual bool processquit(SDL_Event& event)
			{
				return false;
			}
			virtual bool processactive(SDL_Event& event)
			{
				return true;
			}
			virtual bool processmousemotion(SDL_MouseMotionEvent& event)
			{
				return false;
			}
			virtual bool processmousedown(SDL_MouseButtonEvent& event)
			{
				return false;
			}
			virtual bool processmouseup(SDL_MouseButtonEvent& event)
			{
				return false;
			}
			bool processevent(SDL_Event& event)
			{
				bool redraw = false;
				switch (event.type)
				{
					case SDL_VIDEORESIZE:
						//redraw = processvideoresize(event.resize.w, event.resize.h);
						break;
					case SDL_KEYDOWN:
						redraw = processkeydown(event.key.keysym);
						break;
					case SDL_KEYUP:
						redraw = processkeyup(event.key.keysym);
						break;
					case SDL_QUIT:
						redraw = processquit(event);
						break;
					/*
					case SDL_USEREVENT:
						ok = processuser(event.???);
						break;
					*/
					case SDL_ACTIVEEVENT:
						redraw = processactive(event);
						break;
					case SDL_MOUSEMOTION:
						redraw = processmousemotion(event.motion);
						break;
					case SDL_MOUSEBUTTONDOWN:
						redraw = processmousedown(event.button);
						break;
					case SDL_MOUSEBUTTONUP:
						redraw = processmouseup(event.button);
						break;
					/*
					case SDL_JOYAXISMOTION:
						ok = processjoyaxismotion(event.???);
						break;
					case SDL_JOYBALLMOTION:
						ok = processjoyballmotion(event.???);
						break;
					case SDL_JOYHATMOTION:
						ok = processjoyhatmotion(event.???);
						break;
					case SDL_JOYBUTTONDOWN:
						ok = processjoybuttondown(event.???);
						break;
					case SDL_JOYBUTTONUP:
						ok = processjoybuttonup(event.???);
						break;
					case SDL_SYSWMEVENT:
						ok = processsyswm(event.???);
						break;
					*/
					default:
						break;
				}
				return redraw;
			}
			
		};
		
		struct dispatcher
		: handler
		{
			std::list<handler*> handlers;
			std::map<std::string, handler*> registered_handlers;
		
			virtual bool processevent(SDL_Event& event)
			{
				bool handled = false;
				for (std::list<handler*>::iterator i = handlers.begin(); i != handlers.end(); ++i)
				{
					handled = (*i)->processevent(event);
					if (handled) break;
				}
				return handled;
			}
			
			/** Activates the specified handler and puts it in front.
			*/
			virtual void activate_handler(handler* h)
			{
				h->activate();
				handlers.push_front(h);
			}
			/** Activates the handler by name, if registered.
			*/
			virtual void activate_handler(const std::string& name)
			{
				std::map<std::string, handler*>::iterator found = registered_handlers.find(name);
				if (found != registered_handlers.end())
				{
					activate_handler(found->second);
				}
			}
			/** Deactivates the specified handler (and implicitly, all handlers ahead of it).
			*/
			virtual void deactivate_handler(handler* h)
			{}
			/** Deactivates the handler by name (and all handlers ahead of it).
			*/
			virtual void deactivate_handler(const std::string& name)
			{}
			/** Deactivates the front-most handler.
			*/
			virtual void deactivate_handler()
			{
				if (!handlers.empty())
				{
					handlers.front()->deactivate();
					handlers.pop_front();
				}
			}
		};
		
		/** A system framework based on sdl.
		The framework allows the user to get up and running using SDL very quickly.
		Basic functionality is provided, with the means to override it should it be
		necessary.
		
		The only pure virtual function that needs to be implemented by a derived class
		is the draw() method. This method should render whatever the application requires.
		*/
		struct system
		{
			int _systems;
			config _config;
			SDL_Surface* screen;
		
			system()
			: _systems(0)
			{
				if (SDL_Init(0) == -1)
				{
					std::cerr<<"Could not initialize SDL: "<<SDL_GetError()<<std::endl;
					exit(-1);
				}
			}
		
			int init(int systems)
			{
				_systems = systems;
				int result = SDL_InitSubSystem(_systems);
				if (result == -1)
				{
					std::cerr<<"Could not initialise SDL subsystem: "<<SDL_GetError()<<std::endl;
				}
				return result;
			}
		
			int quit(int systems)
			{
				_systems = systems;
				SDL_QuitSubSystem(_systems);
			}
		
			~system()
			{
				SDL_Quit();
			}
		
			SDL_Surface* setvideomode()
			{
				screen = SDL_SetVideoMode(_config.width, _config.height, _config.bpp, _config.flags);
				if (!screen)
				{
					std::cerr<<"Couldn't set video mode: "<<SDL_GetError()<<std::endl;
				}
				return screen;
			}
			SDL_Surface* setvideomode(const config& config_in)
			{
				_config = config_in;
				return setvideomode();
			}
			SDL_Surface* setvideomode(int w, int h, int b, int f = 0)
			{
				_config.width = w; _config.height = h;
				_config.bpp = b;
				_config.flags = f;
				return setvideomode();
			}
			SDL_Surface* setGLvideomode(int w, int h, int f = 0)
			{
				SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
				SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
				SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
				SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
				SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
				// \todo see with SDL_GL_GetAttribute wether we got everything we asked for?
				const SDL_VideoInfo* info = SDL_GetVideoInfo();
				int b = info->vfmt->BitsPerPixel;
				return setvideomode(w, h, b, SDL_OPENGL | f);
			}
			
			/** This dispatches on events.
			Each event is dispatched to a virtual function, which may
			be overridden to implement the derived engine's functionality.
			Sensible defaults are provided as a starting point.
			*/
			virtual bool processevent(SDL_Event& event)
			{
				bool redraw = false;
				switch (event.type)
				{
					case SDL_KEYDOWN:
					{
						redraw = processkeydown(event.key.keysym);
						break;
					}
					case SDL_VIDEORESIZE:
					{
						redraw = processvideoresize(event.resize.w, event.resize.h);
						break;
					}
					case SDL_QUIT:
					{
						running = false;
						break;
					}
				}
				return redraw;
			}
			
			bool running;
			
			/** Starts the system's main loop.
			The main loop waits for events, and upon handling a received event,
			calls the draw() routine to refresh the screen.
			*/
			virtual void start()
			{
				running = true;
				processvideoresize(_config.width, _config.height);
				//draw();
				SDL_Event event;
				while (running)
				{
					draw();
					if (SDL_WaitEvent(&event))
					{
						processevent(event);
					}
				}
			}
			
			virtual bool processkeydown(SDL_keysym& keysym)
			{
				switch (keysym.sym)
				{
					case SDLK_ESCAPE:
						running = false;
						break;
					case SDLK_F12:
						return true;
						break;
					default:
						break;
				}
				return false;
			}
			
			/**
			This function resets the video mode to the new window size.
			
			A re-implementation should do whatever else is required, 
			such as to call glViewport() and set up the camera when using OpenGL, etc.
			
			\note This function may need to re-initialise OpenGL state (glEnable etc.)
			if ever run on Windows, since Windows destroys the OGL context during this resize.
			*/
			virtual bool processvideoresize(int w, int h)
			{
			
				_config.width = w;
				_config.height = h;
				setvideomode();
				return true;
			}
			
			/** The main rendering call.
			For example:
			\codebegin
			void draw()
			{
				// clear the screen and depth buffer
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				// call a function to render the scene
				renderscene(); 
				// flush and swap
				glFlush(); SDL_GL_SwapBuffers();
			}
			\codeend
			*/
			virtual void draw() = 0;
		};
		
	}
}

#endif

